<script lang="ts">
	import { Download, Expand, Loader2, MoveHorizontal, Shrink, ZoomIn, ZoomOut } from 'lucide-svelte'
	import {
		getDocument,
		type PDFDocumentProxy,
		type PDFPageProxy,
		GlobalWorkerOptions
	} from 'pdfjs-dist'

	GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@4.8.69/build/pdf.worker.min.mjs`

	import { fade } from 'svelte/transition'
	import { twMerge } from 'tailwind-merge'
	import { throttle } from '../../utils'
	import { Button } from '../common'
	import { createEventDispatcher } from 'svelte'

	export let source: string | ArrayBuffer | undefined = undefined
	export let zoom: number | undefined = 100
	export let extraButton: any | undefined = undefined
	export let allowFullscreen = false
	let fullscreen = false
	let wrapper: HTMLDivElement | undefined = undefined
	let error: string | undefined = undefined
	let doc: PDFDocumentProxy | undefined = undefined
	let pages: PDFPageProxy[] = []

	let controlsWidth: number | undefined = undefined
	let controlsHeight: number | undefined = undefined
	let pageNumber = 1

	$: if (source == '') {
		resetDoc()
		error = 'Set the "Source" attribute of the PDF component'
	}
	$: zoom && handleZoom()
	$: wrapper && loadDocument(source)
	$: wideView = controlsWidth && controlsWidth > 450

	async function resetDoc() {
		await doc?.destroy()
		doc = undefined
	}

	function handleZoom() {
		if (zoom && wrapper) {
			try {
				renderPdf(false)
			} catch (err) {
				error = err?.message ?? (typeof err === 'string' ? err : 'Error loading PDF')
			}
		}
	}

	const dispatch = createEventDispatcher()

	async function loadDocument(src: string | ArrayBuffer | undefined) {
		console.log('loading documnet')
		if (!src) {
			return
		}
		try {
			dispatch('loading')
			await resetDoc()
			doc = await getDocument(src).promise
			console.log('got doc')
			pageNumber = 1
			await renderPdf(false, false)
			console.log('rendered pdf')
			error = undefined
		} catch (err) {
			await resetDoc()
			error = err?.message ?? (typeof err === 'string' ? err : 'Error loading PDF')
			console.log(err)
		}
		dispatch('loaded')
	}

	async function renderPdf(scaleToViewport = true, resizing = false) {
		if (!(doc && wrapper && zoom)) {
			return
		}
		const scrollPosition = wrapper.scrollTop / wrapper.scrollHeight
		if (!resizing) {
			pages = []
		}
		const nextPages: typeof pages = []
		const nextChildren: HTMLCanvasElement[] = []
		const { width } = wrapper.getBoundingClientRect()
		let scale = zoom / 100
		if (scaleToViewport) {
			const firstViewport = (await doc.getPage(1)).getViewport({ scale: 1 })
			// Rounded to the first integer that is a multiple of 10 and is less than the viewport width
			zoom = Math.floor((width / firstViewport.width) * 10) * 10
			scale = zoom / 100
		}

		for (let i = 0; i < doc.numPages; i++) {
			const canvas = document.createElement('canvas')
			const canvasContext = canvas.getContext('2d')
			if (!canvasContext) {
				console.warn('Could not get canvas context for PDF page ' + i)
				continue
			}
			const page = await doc.getPage(i + 1)
			nextPages.push(page)
			const viewport = page.getViewport({ scale })
			canvas.height = viewport.height
			canvas.width = viewport.width
			canvas.classList.add('mx-auto', 'my-4', 'shadow-sm')
			await page.render({ canvasContext, viewport }).promise
			nextChildren.push(canvas)
		}
		while (wrapper.firstChild) {
			wrapper.removeChild(wrapper.firstChild)
		}
		pages = [...nextPages]
		wrapper.append(...nextChildren)
		wrapper.scrollTo({
			top: scrollPosition * wrapper.scrollHeight
		})
	}

	function scrollToPage(page: number) {
		page = pageNumber = minMax(page, 1, pages.length)
		const offset = (wrapper?.children.item(page - 1) as HTMLCanvasElement | null)?.offsetTop
		if (!offset) {
			return
		}
		//                       controlsHeight + 2px border + half of the top margin
		const padding = (controlsHeight ? controlsHeight + 2 : 0) + 8
		wrapper?.scrollTo({
			top: offset - padding
		})
	}

	const throttledScroll = throttle(onScroll, 400)
	function onScroll() {
		if (!wrapper) {
			return
		}
		const THRESHOLD = 50
		let scrollPosition = wrapper.scrollTop + THRESHOLD + (controlsHeight ?? 0)
		let page = 1
		for (let i = 0; i < pages.length; i++) {
			const canvas = wrapper.children.item(i) as HTMLCanvasElement | null
			if (scrollPosition < (canvas?.offsetTop ?? wrapper.scrollHeight)) {
				break
			}
			page = i + 1
		}
		pageNumber = page
	}

	async function downloadPdf() {
		if (!doc) {
			return
		}
		const data = await doc.saveDocument()
		const url = URL.createObjectURL(new Blob([data.buffer]))
		const link = document.createElement('a')
		link.href = url
		link.download = 'document.pdf'
		link.click()
		URL.revokeObjectURL(url)
	}

	function minMax(value: number, min: number, max: number) {
		if (value < min) {
			return min
		} else if (value > max) {
			return max
		}
		return value
	}
</script>

<div
	class={twMerge('flex flex-col w-full h-full', fullscreen ? 'fixed inset-0 z-[9999]' : 'relative')}
>
	{#if source && zoom}
		{#if pages?.length}
			<div
				bind:clientWidth={controlsWidth}
				bind:clientHeight={controlsHeight}
				class="flex flex-row w-full justify-between overflow-x-auto bg-surface border-b mx-auto py-1"
			>
				<div class="flex justify-start items-center px-2 text-secondary text-sm">
					<Button
						on:click={() => zoom && (zoom -= 10)}
						disabled={!doc}
						size="xs"
						color="light"
						variant="border"
						title="Zoom out"
						aria-label="Zoom out"
						btnClasses="!rounded-r-none !px-2"
					>
						<ZoomOut size={16} />
					</Button>
					{#if wideView}
						<Button
							on:click={() => (zoom = 100)}
							disabled={!doc}
							size="xs"
							color="light"
							variant="border"
							title="Reset zoom"
							aria-label="Reset zoom"
							btnClasses="!w-[50px] !font-medium !rounded-none !border-l-0 !px-1"
						>
							{zoom.toFixed(0)}%
						</Button>
					{/if}
					<Button
						on:click={() => renderPdf(true, true)}
						disabled={!doc}
						size="xs"
						color="light"
						variant="border"
						title="Scale to viewport"
						aria-label="Scale to viewport"
						btnClasses="!rounded-none !border-l-0 !px-2"
					>
						<MoveHorizontal size={16} />
					</Button>
					<Button
						on:click={() => zoom && (zoom += 10)}
						disabled={!doc}
						size="xs"
						color="light"
						variant="border"
						title="Zoom in"
						aria-label="Zoom in"
						btnClasses="!rounded-l-none !px-2 !border-l-0"
					>
						<ZoomIn size={16} />
					</Button>
				</div>
				<div class="center-center px-2 text-secondary text-sm">
					<input
						on:input={({ currentTarget }) => {
							scrollToPage(currentTarget.valueAsNumber)
						}}
						min="1"
						max={pages.length}
						value={pageNumber}
						disabled={!doc}
						type="number"
						class="!w-[45px] !px-1 !py-0"
					/>
					<span class="whitespace-nowrap pl-1">
						/ {pages.length}
					</span>
				</div>
				<div class="flex justify-end items-center px-2 text-secondary text-sm gap-1">
					<Button
						on:click={downloadPdf}
						disabled={!doc}
						size="xs"
						color="light"
						title="Download PDF"
						aria-label="Download PDF"
						btnClasses="!font-medium !px-2"
					>
						<Download size={14} />
					</Button>
					{#if allowFullscreen}
						<Button
							on:click={() => (fullscreen = !fullscreen)}
							disabled={!doc}
							size="xs"
							color="light"
							title="Toggle fullscreen"
							aria-label="Toggle fullscreen"
							btnClasses="!font-medium !px-2"
						>
							{#if fullscreen}
								<Shrink size={14} />
							{:else}
								<Expand size={14} />
							{/if}
						</Button>
					{/if}
				</div>
			</div>
		{:else}
			<div
				out:fade={{ duration: 200 }}
				class="absolute inset-0 center-center flex-col text-center text-sm bg-surface text-secondary"
			>
				<Loader2 class="animate-spin mb-2" />
				Loading PDF
			</div>
		{/if}
		<div
			bind:this={wrapper}
			on:scroll={throttledScroll}
			class={twMerge(
				'w-full h-full overflow-auto',
				$$props.class ?? '',
				// css?.container?.class ?? '',
				'bg-gray-100',
				'wm-pdf'
			)}
			style={$$props.style ?? ''}
		/>
	{/if}
	{#if extraButton}
		<svelte:component this={extraButton} />
	{/if}
	<!-- {#if $mode !== 'preview' && $selectedComponent?.includes(id)}
			<button
				class="fixed z-10 bottom-0 left-0 px-2 py-0.5 bg-indigo-500/90
			hover:bg-indigo-500 focus:bg-indigo-500 duration-200 text-white text-2xs"
				on:click={() => syncZoomValue()}
			>
				Sync zoom value
			</button>
		{/if} -->
	<slot name="extra-button" />
	{#if error}
		<div
			class="absolute inset-0 z-20 center-center
		bg-gray-100 text-center text-secondary text-sm"
		>
			{error}
		</div>
	{/if}
</div>
